package com.xvyy.tingshu.common.login.aspect;

import com.alibaba.fastjson.JSONObject;
import com.xvyy.tingshu.common.constant.PublicConstant;
import com.xvyy.tingshu.common.constant.RedisConstant;
import com.xvyy.tingshu.common.execption.TsException;
import com.xvyy.tingshu.common.login.annotation.TsLogin;
import com.xvyy.tingshu.common.result.ResultCodeEnum;
import com.xvyy.tingshu.common.util.AuthContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;

/**
 * ClassName: LoginAspect
 * Package: com.xvyy.tingshu.common.login.aspect
 *
 * @Description: 登录校验切面类
 * @Author: xvyy
 * @Create: 2025/1/13 - 19:22
 * @Version: v1.0
 */
@Aspect
@Component// 标记为切面组件，自动扫描到IOC容器中
public class LoginAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Around(value = "@annotation(com.xvyy.tingshu.common.login.annotation.TsLogin)")
    public Object checkLogin(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch TODO
        // 前置通知
        // 0. 获取token
        String token = getAccessTokenFromFront();

        // 1. 判断用户注解的required属性是否为true
        Boolean isLogin = getLonginSwitch(pjp);// required为false，则不需要登录即可访问
        if (!isLogin) {
            if (StringUtils.isEmpty(token)) {
                // 如果没有token，并且注解的required为false，则直接放行到目标方法
                return pjp.proceed();//放行到目标方法
            }
        }
        // 2.校验accessToken && 获取用户id
        Long userId = checkAccessTokenAndGetUserId(token);

        AuthContextHolder.setUserId(userId);// 存储用户id到ThreadLocal
        // 3.执行方法
        Object retVal;
        try {
            retVal = pjp.proceed();// 放行到目标方法
        } finally {
            AuthContextHolder.removeUserId();// 移除用户id 从ThreadLocal中
        }

        // stop stopwatch TODO
        // 后置通知
        return retVal;
    }

    /**
     * 获取用户注解的required属性是否为true
     *
     * @param pjp
     * @return
     */
    private Boolean getLonginSwitch(ProceedingJoinPoint pjp) {

        // 1. 获取方法签名对象
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();

        // 2. 获取方法对象
        Method method = methodSignature.getMethod();

        // 3. 获取方法上的注解对象
        TsLogin tsLogin = method.getAnnotation(TsLogin.class);

        // 4. 获取注解的属性值
        return tsLogin.required();

    }


    /**
     * 从前端获取token
     *
     * @return
     */
private String getAccessTokenFromFront() {
    // 1. 获取请求对象
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    // 2. 获取请求对象中的token
    HttpServletRequest request = Objects.requireNonNull(servletRequestAttributes).getRequest();
    // 3. 从请求头中获取token
    return request.getHeader("token");
}

    /**
     * 校验token并且获取用户id
     *
     * @param accessToken
     * @return
     */
private Long checkAccessTokenAndGetUserId(String accessToken) {
    // 1. 校验是否accessToken
    if (StringUtils.isEmpty(accessToken)) {
        throw new TsException(ResultCodeEnum.LOGIN_AUTH);
    }
    // 2. 校验accessToken是否有效
    // 2.1 jwt验签校验  有没有更改过
    Jwt jwt = JwtHelper.decodeAndVerify(accessToken, new RsaVerifier(PublicConstant.PUBLIC_KEY));
    String claims = jwt.getClaims();
    Map map = JSONObject.parseObject(claims, Map.class);
    String openId = (String) map.get("openId");// 用户唯一标识
    Object userId = map.get("userId");
    // 2.2 校验accessToken校验是否和服务端一致
    String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
    String fromRedisJwt = stringRedisTemplate.opsForValue().get(accessTokenKey);
    if (StringUtils.isEmpty(fromRedisJwt) || !accessToken.equals(fromRedisJwt)) {
        throw new TsException(401, "token失效，请重新登录");
    }
    return Long.parseLong(userId + "");
}

}
